1use crate::algebra::{Literal, Term as AlgebraTerm, TriplePattern, Variable};
7use crate::path::PropertyPath;
8use crate::total_float::{TotalF32, TotalF64};
9use anyhow::{anyhow, bail, Result};
10use base64::Engine;
11use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, Timelike};
12use oxirs_core::model::NamedNode;
13use std::cmp::Ordering;
14use std::collections::HashMap;
15use std::fmt;
16
17pub const XSD_NS: &str = "http://www.w3.org/2001/XMLSchema#";
19
20pub const RDF_NS: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
22
23pub mod xsd {
25
26 pub const STRING: &str = "http://www.w3.org/2001/XMLSchema#string";
27 pub const BOOLEAN: &str = "http://www.w3.org/2001/XMLSchema#boolean";
28 pub const DECIMAL: &str = "http://www.w3.org/2001/XMLSchema#decimal";
29 pub const INTEGER: &str = "http://www.w3.org/2001/XMLSchema#integer";
30 pub const DOUBLE: &str = "http://www.w3.org/2001/XMLSchema#double";
31 pub const FLOAT: &str = "http://www.w3.org/2001/XMLSchema#float";
32 pub const DATE: &str = "http://www.w3.org/2001/XMLSchema#date";
33 pub const TIME: &str = "http://www.w3.org/2001/XMLSchema#time";
34 pub const DATE_TIME: &str = "http://www.w3.org/2001/XMLSchema#dateTime";
35 pub const DATE_TIME_STAMP: &str = "http://www.w3.org/2001/XMLSchema#dateTimeStamp";
36 pub const DURATION: &str = "http://www.w3.org/2001/XMLSchema#duration";
37 pub const BYTE: &str = "http://www.w3.org/2001/XMLSchema#byte";
38 pub const SHORT: &str = "http://www.w3.org/2001/XMLSchema#short";
39 pub const INT: &str = "http://www.w3.org/2001/XMLSchema#int";
40 pub const LONG: &str = "http://www.w3.org/2001/XMLSchema#long";
41 pub const UNSIGNED_BYTE: &str = "http://www.w3.org/2001/XMLSchema#unsignedByte";
42 pub const UNSIGNED_SHORT: &str = "http://www.w3.org/2001/XMLSchema#unsignedShort";
43 pub const UNSIGNED_INT: &str = "http://www.w3.org/2001/XMLSchema#unsignedInt";
44 pub const UNSIGNED_LONG: &str = "http://www.w3.org/2001/XMLSchema#unsignedLong";
45 pub const POSITIVE_INTEGER: &str = "http://www.w3.org/2001/XMLSchema#positiveInteger";
46 pub const NON_NEGATIVE_INTEGER: &str = "http://www.w3.org/2001/XMLSchema#nonNegativeInteger";
47 pub const NEGATIVE_INTEGER: &str = "http://www.w3.org/2001/XMLSchema#negativeInteger";
48 pub const NON_POSITIVE_INTEGER: &str = "http://www.w3.org/2001/XMLSchema#nonPositiveInteger";
49 pub const HEX_BINARY: &str = "http://www.w3.org/2001/XMLSchema#hexBinary";
50 pub const BASE64_BINARY: &str = "http://www.w3.org/2001/XMLSchema#base64Binary";
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub enum Term {
56 Iri(String),
58 BlankNode(String),
60 Literal(LiteralValue),
62 Variable(String),
64 QuotedTriple(Box<QuotedTripleValue>),
66 PropertyPath(PropertyPath),
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
72pub struct QuotedTripleValue {
73 pub subject: Term,
74 pub predicate: Term,
75 pub object: Term,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Hash)]
80pub struct LiteralValue {
81 pub lexical_form: String,
83 pub datatype: String,
85 pub language_tag: Option<String>,
87 parsed_value: ParsedValue,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Hash)]
93enum ParsedValue {
94 String(String),
95 Boolean(bool),
96 Integer(i64),
97 Decimal(TotalF64),
98 Float(TotalF32),
99 Double(TotalF64),
100 DateTime(i64), Date(i32), Time(i64), #[allow(dead_code)]
104 Duration(i64), Binary(Vec<u8>),
106 Other,
107}
108
109impl Term {
110 pub fn iri(iri: &str) -> Self {
112 Term::Iri(iri.to_string())
113 }
114
115 pub fn blank_node(id: &str) -> Self {
117 Term::BlankNode(id.to_string())
118 }
119
120 pub fn literal(value: &str) -> Self {
122 Term::Literal(LiteralValue::new_simple(value))
123 }
124
125 pub fn typed_literal(value: &str, datatype: &str) -> Result<Self> {
127 Ok(Term::Literal(LiteralValue::new_typed(value, datatype)?))
128 }
129
130 pub fn lang_literal(value: &str, lang: &str) -> Self {
132 Term::Literal(LiteralValue::new_lang(value, lang))
133 }
134
135 pub fn variable(name: &str) -> Self {
137 Term::Variable(name.to_string())
138 }
139
140 pub fn quoted_triple(subject: Term, predicate: Term, object: Term) -> Self {
142 Term::QuotedTriple(Box::new(QuotedTripleValue {
143 subject,
144 predicate,
145 object,
146 }))
147 }
148
149 pub fn property_path(path: PropertyPath) -> Self {
151 Term::PropertyPath(path)
152 }
153
154 pub fn is_variable(&self) -> bool {
156 matches!(self, Term::Variable(_))
157 }
158
159 pub fn is_ground(&self) -> bool {
161 !self.is_variable()
162 }
163
164 pub fn is_iri(&self) -> bool {
166 matches!(self, Term::Iri(_))
167 }
168
169 pub fn is_blank_node(&self) -> bool {
171 matches!(self, Term::BlankNode(_))
172 }
173
174 pub fn is_literal(&self) -> bool {
176 matches!(self, Term::Literal(_))
177 }
178
179 pub fn is_quoted_triple(&self) -> bool {
181 matches!(self, Term::QuotedTriple(_))
182 }
183
184 pub fn is_property_path(&self) -> bool {
186 matches!(self, Term::PropertyPath(_))
187 }
188}
189
190impl LiteralValue {
191 pub fn new_simple(value: &str) -> Self {
193 Self {
194 lexical_form: value.to_string(),
195 datatype: xsd::STRING.to_string(),
196 language_tag: None,
197 parsed_value: ParsedValue::String(value.to_string()),
198 }
199 }
200
201 pub fn new_lang(value: &str, lang: &str) -> Self {
203 Self {
204 lexical_form: value.to_string(),
205 datatype: RDF_NS.to_string() + "langString",
206 language_tag: Some(lang.to_string()),
207 parsed_value: ParsedValue::String(value.to_string()),
208 }
209 }
210
211 pub fn new_typed(value: &str, datatype: &str) -> Result<Self> {
213 let parsed_value = Self::parse_value(value, datatype)?;
214 Ok(Self {
215 lexical_form: value.to_string(),
216 datatype: datatype.to_string(),
217 language_tag: None,
218 parsed_value,
219 })
220 }
221
222 fn parse_value(value: &str, datatype: &str) -> Result<ParsedValue> {
224 Ok(match datatype {
225 xsd::STRING => ParsedValue::String(value.to_string()),
226 xsd::BOOLEAN => {
227 let b = match value {
228 "true" | "1" => true,
229 "false" | "0" => false,
230 _ => bail!("Invalid boolean value: {value}"),
231 };
232 ParsedValue::Boolean(b)
233 }
234 xsd::INTEGER | xsd::LONG | xsd::INT | xsd::SHORT | xsd::BYTE => {
235 let i = value
236 .parse::<i64>()
237 .map_err(|_| anyhow!("Invalid integer value: {value}"))?;
238 ParsedValue::Integer(i)
239 }
240 xsd::DECIMAL => {
241 let d = value
242 .parse::<f64>()
243 .map_err(|_| anyhow!("Invalid decimal value: {value}"))?;
244 ParsedValue::Decimal(TotalF64(d))
245 }
246 xsd::FLOAT => {
247 let f = value
248 .parse::<f32>()
249 .map_err(|_| anyhow!("Invalid float value: {value}"))?;
250 ParsedValue::Float(TotalF32(f))
251 }
252 xsd::DOUBLE => {
253 let d = value
254 .parse::<f64>()
255 .map_err(|_| anyhow!("Invalid double value: {value}"))?;
256 ParsedValue::Double(TotalF64(d))
257 }
258 xsd::DATE_TIME | xsd::DATE_TIME_STAMP => {
259 let dt = DateTime::parse_from_rfc3339(value)
260 .map_err(|_| anyhow!("Invalid dateTime value: {value}"))?;
261 ParsedValue::DateTime(dt.timestamp_nanos_opt().unwrap_or(0))
262 }
263 xsd::DATE => {
264 let date = NaiveDate::parse_from_str(value, "%Y-%m-%d")
265 .map_err(|_| anyhow!("Invalid date value: {value}"))?;
266 ParsedValue::Date(date.num_days_from_ce())
267 }
268 xsd::TIME => {
269 let time = NaiveTime::parse_from_str(value, "%H:%M:%S%.f")
270 .map_err(|_| anyhow!("Invalid time value: {value}"))?;
271 ParsedValue::Time(
272 time.num_seconds_from_midnight() as i64 * 1_000_000_000
273 + time.nanosecond() as i64,
274 )
275 }
276 xsd::HEX_BINARY => {
277 let bytes =
278 hex::decode(value).map_err(|_| anyhow!("Invalid hexBinary value: {value}"))?;
279 ParsedValue::Binary(bytes)
280 }
281 xsd::BASE64_BINARY => {
282 let bytes = base64::engine::general_purpose::STANDARD
283 .decode(value)
284 .map_err(|_| anyhow!("Invalid base64Binary value: {value}"))?;
285 ParsedValue::Binary(bytes)
286 }
287 _ => ParsedValue::Other,
288 })
289 }
290
291 pub fn effective_boolean_value(&self) -> Result<bool> {
293 match &self.parsed_value {
294 ParsedValue::Boolean(b) => Ok(*b),
295 ParsedValue::String(s) => Ok(!s.is_empty()),
296 ParsedValue::Integer(i) => Ok(*i != 0),
297 ParsedValue::Decimal(d) => Ok(d.0 != 0.0),
298 ParsedValue::Float(f) => Ok(f.0 != 0.0),
299 ParsedValue::Double(d) => Ok(d.0 != 0.0),
300 _ => Ok(true),
301 }
302 }
303
304 pub fn to_numeric(&self) -> Result<NumericValue> {
306 match &self.parsed_value {
307 ParsedValue::Integer(i) => Ok(NumericValue::Integer(*i)),
308 ParsedValue::Decimal(d) => Ok(NumericValue::Decimal(d.0)),
309 ParsedValue::Float(f) => Ok(NumericValue::Float(f.0 as f64)),
310 ParsedValue::Double(d) => Ok(NumericValue::Double(d.0)),
311 ParsedValue::Boolean(b) => Ok(NumericValue::Integer(if *b { 1 } else { 0 })),
312 ParsedValue::String(s) => {
313 if let Ok(i) = s.parse::<i64>() {
315 Ok(NumericValue::Integer(i))
316 } else if let Ok(d) = s.parse::<f64>() {
317 Ok(NumericValue::Double(d))
318 } else {
319 bail!("Cannot convert string '{s}' to numeric")
320 }
321 }
322 _ => bail!("Cannot convert {dt} to numeric", dt = self.datatype),
323 }
324 }
325
326 pub fn is_numeric(&self) -> bool {
328 matches!(
329 &self.parsed_value,
330 ParsedValue::Integer(_)
331 | ParsedValue::Decimal(_)
332 | ParsedValue::Float(_)
333 | ParsedValue::Double(_)
334 )
335 }
336}
337
338#[derive(Debug, Clone, PartialEq)]
340pub enum NumericValue {
341 Integer(i64),
342 Decimal(f64),
343 Float(f64),
344 Double(f64),
345}
346
347impl NumericValue {
348 pub fn promote_with(&self, other: &NumericValue) -> (NumericValue, NumericValue) {
350 use NumericValue::*;
351 match (self, other) {
352 (Integer(a), Integer(b)) => (Integer(*a), Integer(*b)),
353 (Integer(a), Decimal(b)) => (Decimal(*a as f64), Decimal(*b)),
354 (Integer(a), Float(b)) => (Float(*a as f64), Float(*b)),
355 (Integer(a), Double(b)) => (Double(*a as f64), Double(*b)),
356 (Decimal(a), Integer(b)) => (Decimal(*a), Decimal(*b as f64)),
357 (Decimal(a), Decimal(b)) => (Decimal(*a), Decimal(*b)),
358 (Decimal(a), Float(b)) => (Float(*a), Float(*b)),
359 (Decimal(a), Double(b)) => (Double(*a), Double(*b)),
360 (Float(a), Integer(b)) => (Float(*a), Float(*b as f64)),
361 (Float(a), Decimal(b)) => (Float(*a), Float(*b)),
362 (Float(a), Float(b)) => (Float(*a), Float(*b)),
363 (Float(a), Double(b)) => (Double(*a), Double(*b)),
364 (Double(a), Integer(b)) => (Double(*a), Double(*b as f64)),
365 (Double(a), Decimal(b)) => (Double(*a), Double(*b)),
366 (Double(a), Float(b)) => (Double(*a), Double(*b)),
367 (Double(a), Double(b)) => (Double(*a), Double(*b)),
368 }
369 }
370
371 pub fn to_term(&self) -> Term {
373 match self {
374 NumericValue::Integer(i) => Term::typed_literal(&i.to_string(), xsd::INTEGER)
375 .expect("integer to xsd:integer literal should always succeed"),
376 NumericValue::Decimal(d) => Term::typed_literal(&d.to_string(), xsd::DECIMAL)
377 .expect("decimal to xsd:decimal literal should always succeed"),
378 NumericValue::Float(f) => Term::typed_literal(&f.to_string(), xsd::FLOAT)
379 .expect("float to xsd:float literal should always succeed"),
380 NumericValue::Double(d) => Term::typed_literal(&d.to_string(), xsd::DOUBLE)
381 .expect("double to xsd:double literal should always succeed"),
382 }
383 }
384}
385
386impl PartialOrd for Term {
388 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
389 Some(self.cmp(other))
390 }
391}
392
393impl Ord for Term {
394 fn cmp(&self, other: &Self) -> Ordering {
395 use Term::*;
396
397 match (self, other) {
399 (Variable(_), Variable(_)) => Ordering::Equal,
400 (Variable(_), _) => Ordering::Less,
401 (_, Variable(_)) => Ordering::Greater,
402
403 (BlankNode(a), BlankNode(b)) => a.cmp(b),
404 (BlankNode(_), _) => Ordering::Less,
405 (_, BlankNode(_)) => Ordering::Greater,
406
407 (Iri(a), Iri(b)) => a.cmp(b),
408 (Iri(_), Literal(_) | QuotedTriple(_) | PropertyPath(_)) => Ordering::Less,
409 (Literal(_) | QuotedTriple(_) | PropertyPath(_), Iri(_)) => Ordering::Greater,
410
411 (Literal(a), Literal(b)) => a.cmp(b),
412 (Literal(_), QuotedTriple(_) | PropertyPath(_)) => Ordering::Less,
413 (QuotedTriple(_) | PropertyPath(_), Literal(_)) => Ordering::Greater,
414
415 (QuotedTriple(a), QuotedTriple(b)) => a.cmp(b),
416 (QuotedTriple(_), PropertyPath(_)) => Ordering::Less,
417 (PropertyPath(_), QuotedTriple(_)) => Ordering::Greater,
418
419 (PropertyPath(a), PropertyPath(b)) => a.cmp(b),
420 }
421 }
422}
423
424impl PartialOrd for LiteralValue {
425 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
426 Some(self.cmp(other))
427 }
428}
429
430impl Ord for LiteralValue {
431 fn cmp(&self, other: &Self) -> Ordering {
432 match (&self.language_tag, &other.language_tag) {
434 (Some(a), Some(b)) => match a.cmp(b) {
435 Ordering::Equal => self.lexical_form.cmp(&other.lexical_form),
436 ord => ord,
437 },
438 (Some(_), None) => Ordering::Less,
439 (None, Some(_)) => Ordering::Greater,
440 (None, None) => {
441 match self.datatype.cmp(&other.datatype) {
443 Ordering::Equal => self.compare_same_datatype(other),
444 ord => ord,
445 }
446 }
447 }
448 }
449}
450
451impl LiteralValue {
452 fn compare_same_datatype(&self, other: &Self) -> Ordering {
453 match (&self.parsed_value, &other.parsed_value) {
454 (ParsedValue::Boolean(a), ParsedValue::Boolean(b)) => a.cmp(b),
455 (ParsedValue::Integer(a), ParsedValue::Integer(b)) => a.cmp(b),
456 (ParsedValue::Decimal(a), ParsedValue::Decimal(b)) => a.cmp(b),
457 (ParsedValue::Float(a), ParsedValue::Float(b)) => a.cmp(b),
458 (ParsedValue::Double(a), ParsedValue::Double(b)) => a.cmp(b),
459 (ParsedValue::DateTime(a), ParsedValue::DateTime(b)) => a.cmp(b),
460 (ParsedValue::Date(a), ParsedValue::Date(b)) => a.cmp(b),
461 (ParsedValue::Time(a), ParsedValue::Time(b)) => a.cmp(b),
462 (ParsedValue::Duration(a), ParsedValue::Duration(b)) => a.cmp(b),
463 (ParsedValue::Binary(a), ParsedValue::Binary(b)) => a.cmp(b),
464 _ => self.lexical_form.cmp(&other.lexical_form),
465 }
466 }
467}
468
469#[derive(Debug, Clone, Default)]
471pub struct BindingContext {
472 bindings: HashMap<Variable, Term>,
474 scopes: Vec<HashMap<Variable, Term>>,
476}
477
478impl BindingContext {
479 pub fn new() -> Self {
481 Self::default()
482 }
483
484 pub fn bind(&mut self, var: &str, term: Term) {
486 if let Ok(variable) = Variable::new(var) {
487 self.bindings.insert(variable, term);
488 }
489 }
490
491 pub fn get(&self, var: &str) -> Option<&Term> {
493 if let Ok(variable) = Variable::new(var) {
495 if let Some(term) = self.bindings.get(&variable) {
496 return Some(term);
497 }
498 }
499
500 for scope in self.scopes.iter().rev() {
502 if let Ok(variable) = Variable::new(var) {
503 if let Some(term) = scope.get(&variable) {
504 return Some(term);
505 }
506 }
507 }
508
509 None
510 }
511
512 pub fn is_bound(&self, var: &str) -> bool {
514 self.get(var).is_some()
515 }
516
517 pub fn push_scope(&mut self) {
519 let current = std::mem::take(&mut self.bindings);
520 self.scopes.push(current);
521 }
522
523 pub fn pop_scope(&mut self) {
525 if let Some(scope) = self.scopes.pop() {
526 self.bindings = scope;
527 }
528 }
529
530 pub fn variables(&self) -> Vec<&str> {
532 let mut vars: Vec<_> = self.bindings.keys().map(|s| s.as_str()).collect();
533
534 for scope in &self.scopes {
535 for var in scope.keys() {
536 if !vars.contains(&var.as_str()) {
537 vars.push(var.as_str());
538 }
539 }
540 }
541
542 vars
543 }
544
545 pub fn apply(&self, term: &Term) -> Term {
547 match term {
548 Term::Variable(var) => self.get(var).cloned().unwrap_or_else(|| term.clone()),
549 _ => term.clone(),
550 }
551 }
552}
553
554pub fn matches_pattern(pattern: &Term, term: &Term, bindings: &mut BindingContext) -> bool {
556 match (pattern, term) {
557 (Term::Variable(var), _) => {
558 if let Some(bound) = bindings.get(var) {
560 bound == term
561 } else {
562 bindings.bind(var, term.clone());
564 true
565 }
566 }
567 (Term::Iri(p), Term::Iri(t)) => p == t,
568 (Term::BlankNode(p), Term::BlankNode(t)) => p == t,
569 (Term::Literal(p), Term::Literal(t)) => p == t,
570 (Term::QuotedTriple(p), Term::QuotedTriple(t)) => {
571 matches_pattern(&p.subject, &t.subject, bindings)
572 && matches_pattern(&p.predicate, &t.predicate, bindings)
573 && matches_pattern(&p.object, &t.object, bindings)
574 }
575 (Term::PropertyPath(p), Term::PropertyPath(t)) => p == t,
576 _ => false,
577 }
578}
579
580impl fmt::Display for Term {
581 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582 match self {
583 Term::Iri(iri) => write!(f, "<{iri}>"),
584 Term::BlankNode(id) => write!(f, "_{id}"),
585 Term::Literal(lit) => write!(f, "{lit}"),
586 Term::Variable(var) => write!(f, "?{var}"),
587 Term::QuotedTriple(triple) => {
588 write!(
589 f,
590 "<<{} {} {}>>",
591 triple.subject, triple.predicate, triple.object
592 )
593 }
594 Term::PropertyPath(path) => write!(f, "{path}"),
595 }
596 }
597}
598
599impl fmt::Display for LiteralValue {
600 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601 write!(f, "\"{}\"", self.lexical_form)?;
602 if let Some(lang) = &self.language_tag {
603 write!(f, "@{lang}")?;
604 } else if self.datatype != xsd::STRING {
605 write!(f, "^^<{dt}>", dt = self.datatype)?;
606 }
607 Ok(())
608 }
609}
610
611impl Term {
612 pub fn from_algebra_term(algebra_term: &AlgebraTerm) -> Self {
614 match algebra_term {
615 AlgebraTerm::Iri(iri) => Term::iri(iri.as_str()),
616 AlgebraTerm::Literal(lit) => {
617 if let Some(lang) = &lit.language {
618 Term::lang_literal(&lit.value, lang)
619 } else if let Some(datatype) = &lit.datatype {
620 Term::typed_literal(&lit.value, datatype.as_str())
621 .unwrap_or_else(|_| Term::literal(&lit.value))
622 } else {
623 Term::literal(&lit.value)
624 }
625 }
626 AlgebraTerm::BlankNode(bn) => Term::blank_node(bn),
627 AlgebraTerm::Variable(var) => Term::variable(var.as_str()),
628 AlgebraTerm::QuotedTriple(quoted_triple) => {
629 Term::QuotedTriple(Box::new(QuotedTripleValue {
630 subject: Term::from_algebra_term("ed_triple.subject),
631 predicate: Term::from_algebra_term("ed_triple.predicate),
632 object: Term::from_algebra_term("ed_triple.object),
633 }))
634 }
635 AlgebraTerm::PropertyPath(path) => {
636 Term::PropertyPath(Self::convert_algebra_to_path_property_path(path))
637 }
638 }
639 }
640
641 pub fn to_algebra_term(&self) -> AlgebraTerm {
643 match self {
644 Term::Iri(iri) => AlgebraTerm::Iri(NamedNode::new_unchecked(iri)),
645 Term::BlankNode(bn) => AlgebraTerm::BlankNode(bn.clone()),
646 Term::Literal(lit_val) => AlgebraTerm::Literal(Literal {
647 value: lit_val.lexical_form.clone(),
648 language: lit_val.language_tag.clone(),
649 datatype: if lit_val.datatype != xsd::STRING {
650 Some(NamedNode::new_unchecked(&lit_val.datatype))
651 } else {
652 None
653 },
654 }),
655 Term::Variable(var) => AlgebraTerm::Variable(
656 Variable::new(var).expect("variable name should be valid for algebra conversion"),
657 ),
658 Term::QuotedTriple(triple) => AlgebraTerm::QuotedTriple(Box::new(TriplePattern {
659 subject: triple.subject.to_algebra_term(),
660 predicate: triple.predicate.to_algebra_term(),
661 object: triple.object.to_algebra_term(),
662 })),
663 Term::PropertyPath(path) => {
664 AlgebraTerm::PropertyPath(Self::convert_path_to_algebra_property_path(path))
665 }
666 }
667 }
668
669 pub fn effective_boolean_value(&self) -> Result<bool> {
671 match self {
672 Term::Literal(lit_val) => match lit_val.datatype.as_str() {
673 xsd::BOOLEAN => Ok(lit_val.lexical_form == "true" || lit_val.lexical_form == "1"),
674 xsd::STRING => Ok(!lit_val.lexical_form.is_empty()),
675 dt if dt.starts_with(XSD_NS)
676 && (dt.ends_with("integer")
677 || dt.ends_with("decimal")
678 || dt.ends_with("double")
679 || dt.ends_with("float")) =>
680 {
681 let val = lit_val.lexical_form.parse::<f64>().unwrap_or(0.0);
682 Ok(val != 0.0 && !val.is_nan())
683 }
684 _ => Ok(!lit_val.lexical_form.is_empty()),
685 },
686 Term::Iri(_) | Term::BlankNode(_) | Term::QuotedTriple(_) | Term::PropertyPath(_) => {
687 Ok(true)
688 }
689 Term::Variable(_) => bail!("Cannot evaluate variable as boolean"),
690 }
691 }
692
693 pub fn to_numeric(&self) -> Result<NumericValue> {
695 match self {
696 Term::Literal(lit_val) => lit_val.to_numeric(),
697 Term::Iri(_) | Term::BlankNode(_) | Term::QuotedTriple(_) | Term::PropertyPath(_) => {
698 bail!("Cannot convert IRI, blank node, quoted triple, or property path to numeric")
699 }
700 Term::Variable(_) => bail!("Cannot convert unbound variable to numeric"),
701 }
702 }
703
704 fn convert_algebra_to_path_property_path(
706 algebra_path: &crate::algebra::PropertyPath,
707 ) -> crate::path::PropertyPath {
708 use crate::algebra::PropertyPath as AlgPath;
709 use crate::algebra::Term as AlgTerm;
710 use crate::path::PropertyPath as PathPath;
711
712 match algebra_path {
713 AlgPath::Iri(iri) => PathPath::Direct(AlgTerm::Iri(iri.clone())),
714 AlgPath::Variable(var) => PathPath::Direct(AlgTerm::Variable(var.clone())),
715 AlgPath::Inverse(path) => {
716 PathPath::Inverse(Box::new(Self::convert_algebra_to_path_property_path(path)))
717 }
718 AlgPath::Sequence(left, right) => PathPath::Sequence(
719 Box::new(Self::convert_algebra_to_path_property_path(left)),
720 Box::new(Self::convert_algebra_to_path_property_path(right)),
721 ),
722 AlgPath::Alternative(left, right) => PathPath::Alternative(
723 Box::new(Self::convert_algebra_to_path_property_path(left)),
724 Box::new(Self::convert_algebra_to_path_property_path(right)),
725 ),
726 AlgPath::ZeroOrMore(path) => {
727 PathPath::ZeroOrMore(Box::new(Self::convert_algebra_to_path_property_path(path)))
728 }
729 AlgPath::OneOrMore(path) => {
730 PathPath::OneOrMore(Box::new(Self::convert_algebra_to_path_property_path(path)))
731 }
732 AlgPath::ZeroOrOne(path) => {
733 PathPath::ZeroOrOne(Box::new(Self::convert_algebra_to_path_property_path(path)))
734 }
735 AlgPath::NegatedPropertySet(_) => {
736 PathPath::Direct(AlgTerm::Iri(oxirs_core::model::NamedNode::new_unchecked(
739 "<urn:negated-property-set>",
740 )))
741 }
742 }
743 }
744
745 fn convert_path_to_algebra_property_path(
747 path_path: &crate::path::PropertyPath,
748 ) -> crate::algebra::PropertyPath {
749 use crate::algebra::PropertyPath as AlgPath;
750 use crate::algebra::Term as AlgTerm;
751 use crate::path::PropertyPath as PathPath;
752
753 match path_path {
754 PathPath::Direct(term) => match term {
755 AlgTerm::Iri(iri) => {
756 AlgPath::Iri(oxirs_core::model::NamedNode::new_unchecked(iri.as_str()))
757 }
758 AlgTerm::Variable(var) => AlgPath::Variable(var.clone()),
759 _ => AlgPath::Iri(oxirs_core::model::NamedNode::new_unchecked("<urn:unknown>")),
760 },
761 PathPath::Inverse(path) => {
762 AlgPath::Inverse(Box::new(Self::convert_path_to_algebra_property_path(path)))
763 }
764 PathPath::Sequence(left, right) => AlgPath::Sequence(
765 Box::new(Self::convert_path_to_algebra_property_path(left)),
766 Box::new(Self::convert_path_to_algebra_property_path(right)),
767 ),
768 PathPath::Alternative(left, right) => AlgPath::Alternative(
769 Box::new(Self::convert_path_to_algebra_property_path(left)),
770 Box::new(Self::convert_path_to_algebra_property_path(right)),
771 ),
772 PathPath::ZeroOrMore(path) => {
773 AlgPath::ZeroOrMore(Box::new(Self::convert_path_to_algebra_property_path(path)))
774 }
775 PathPath::OneOrMore(path) => {
776 AlgPath::OneOrMore(Box::new(Self::convert_path_to_algebra_property_path(path)))
777 }
778 PathPath::ZeroOrOne(path) => {
779 AlgPath::ZeroOrOne(Box::new(Self::convert_path_to_algebra_property_path(path)))
780 }
781 PathPath::NegatedPropertySet(terms) => {
782 let property_paths: Vec<crate::algebra::PropertyPath> = terms
784 .iter()
785 .filter_map(|term| match term {
786 AlgTerm::Iri(iri) => Some(AlgPath::Iri(iri.clone())),
787 AlgTerm::Variable(var) => Some(AlgPath::Variable(var.clone())),
788 _ => None,
789 })
790 .collect();
791 AlgPath::NegatedPropertySet(property_paths)
792 }
793 }
794 }
795}
796
797#[cfg(test)]
798mod tests {
799 use super::*;
800
801 #[test]
802 fn test_term_creation() {
803 let iri = Term::iri("http://example.org/foo");
804 assert!(iri.is_iri());
805
806 let blank = Term::blank_node("b1");
807 assert!(blank.is_blank_node());
808
809 let lit = Term::literal("hello");
810 assert!(lit.is_literal());
811
812 let var = Term::variable("x");
813 assert!(var.is_variable());
814 }
815
816 #[test]
817 fn test_typed_literals() {
818 let int_lit = Term::typed_literal("42", xsd::INTEGER).unwrap();
819 assert!(matches!(int_lit, Term::Literal(_)));
820
821 let bool_lit = Term::typed_literal("true", xsd::BOOLEAN).unwrap();
822 assert!(bool_lit.effective_boolean_value().unwrap());
823
824 let date_lit = Term::typed_literal("2023-01-01", xsd::DATE).unwrap();
825 assert!(matches!(date_lit, Term::Literal(_)));
826 }
827
828 #[test]
829 fn test_numeric_conversion() {
830 let int_term = Term::typed_literal("42", xsd::INTEGER).unwrap();
831 let num = int_term.to_numeric().unwrap();
832 assert_eq!(num, NumericValue::Integer(42));
833
834 let float_term = Term::typed_literal("3.14", xsd::FLOAT).unwrap();
835 let num = float_term.to_numeric().unwrap();
836 assert!(matches!(num, NumericValue::Float(_)));
837 }
838
839 #[test]
840 fn test_term_ordering() {
841 let var = Term::variable("x");
842 let blank = Term::blank_node("b1");
843 let iri = Term::iri("http://example.org");
844 let lit = Term::literal("test");
845
846 assert!(var < blank);
847 assert!(blank < iri);
848 assert!(iri < lit);
849 }
850
851 #[test]
852 fn test_binding_context() {
853 let mut ctx = BindingContext::new();
854
855 let term = Term::literal("value");
856 ctx.bind("x", term.clone());
857
858 assert!(ctx.is_bound("x"));
859 assert_eq!(ctx.get("x"), Some(&term));
860
861 ctx.push_scope();
862 ctx.bind("y", Term::literal("other"));
863
864 assert!(ctx.is_bound("x")); assert!(ctx.is_bound("y"));
866
867 ctx.pop_scope();
868 assert!(ctx.is_bound("x"));
869 assert!(!ctx.is_bound("y")); }
871
872 #[test]
873 fn test_pattern_matching() {
874 let mut ctx = BindingContext::new();
875
876 let pattern = Term::variable("x");
877 let term = Term::literal("test");
878
879 assert!(matches_pattern(&pattern, &term, &mut ctx));
880 assert_eq!(ctx.get("x"), Some(&term));
881
882 let term2 = Term::literal("other");
884 assert!(!matches_pattern(&pattern, &term2, &mut ctx));
885 }
886
887 #[test]
888 fn test_quoted_triple_term() {
889 let subject = Term::iri("http://example.org/subject");
890 let predicate = Term::iri("http://example.org/predicate");
891 let object = Term::literal("object");
892
893 let quoted_triple = Term::quoted_triple(subject.clone(), predicate.clone(), object.clone());
894 assert!(quoted_triple.is_quoted_triple());
895
896 let display = format!("{quoted_triple}");
898 assert!(display.starts_with("<<"));
899 assert!(display.ends_with(">>"));
900 }
901
902 #[test]
903 fn test_property_path_term() {
904 use crate::path::PropertyPath;
905
906 let direct_path = PropertyPath::Direct(crate::algebra::Term::Iri(
907 crate::algebra::Iri::new_unchecked("http://example.org/prop"),
908 ));
909 let path_term = Term::property_path(direct_path);
910 assert!(path_term.is_property_path());
911 }
912
913 #[test]
914 fn test_term_ordering_with_new_variants() {
915 let var = Term::variable("x");
916 let blank = Term::blank_node("b1");
917 let iri = Term::iri("http://example.org");
918 let lit = Term::literal("test");
919 let quoted = Term::quoted_triple(
920 Term::iri("http://example.org/s"),
921 Term::iri("http://example.org/p"),
922 Term::iri("http://example.org/o"),
923 );
924 let path = Term::property_path(crate::path::PropertyPath::Direct(
925 crate::algebra::Term::Iri(crate::algebra::Iri::new_unchecked(
926 "http://example.org/prop",
927 )),
928 ));
929
930 assert!(var < blank);
932 assert!(blank < iri);
933 assert!(iri < lit);
934 assert!(lit < quoted);
935 assert!(quoted < path);
936 }
937}