Skip to main content

nibli_import/
lib.rs

1//! Knowledge base import/export for standard formats.
2//!
3//! Provides RDF Turtle import, OWL class hierarchy import, and fact export.
4//! Uses `nibli-engine` to inject facts via `assert_fact_direct`.
5
6pub mod export;
7pub mod owl;
8pub mod rdf;
9
10use nibli_engine::NibliEngine;
11
12/// Import RDF Turtle triples into the engine's KB.
13/// Parses the Turtle text, then for each triple:
14/// - `rdfs:subClassOf` → `declare_subsort`
15/// - `rdf:type` → `declare_entity_sort`
16/// - Other → `assert_fact_direct(predicate, [subject, object])`
17///
18/// Returns the number of facts/declarations imported.
19pub fn import_turtle(engine: &NibliEngine, turtle_text: &str) -> Result<usize, String> {
20    let triples = rdf::parse_turtle(turtle_text)?;
21    owl::import_owl_classes(engine, &triples)
22}
23
24/// Import raw RDF triples (without OWL class handling) into the KB.
25/// Every triple becomes a 2-argument fact: `predicate(subject, object)`.
26///
27/// Returns the number of facts asserted.
28pub fn import_triples_raw(engine: &NibliEngine, turtle_text: &str) -> Result<usize, String> {
29    let triples = rdf::parse_turtle(turtle_text)?;
30    let mut count = 0;
31    for triple in &triples {
32        let rel = triple.predicate.local_name().to_string();
33        let args = vec![
34            term_to_logical(&triple.subject),
35            term_to_logical(&triple.object),
36        ];
37        engine
38            .assert_fact_direct(rel, args)
39            .map_err(|e| e.to_string())?;
40        count += 1;
41    }
42    Ok(count)
43}
44
45/// Export all active KB facts as labeled lines.
46pub fn export_facts(engine: &NibliEngine) -> Result<String, String> {
47    export::export_fact_labels(engine)
48}
49
50fn term_to_logical(term: &rdf::Term) -> nibli_engine::EngineLogicalTerm {
51    match term {
52        rdf::Term::Iri(_) => {
53            nibli_engine::EngineLogicalTerm::Constant(term.local_name().to_string())
54        }
55        rdf::Term::StringLiteral(s) => nibli_engine::EngineLogicalTerm::Constant(s.clone()),
56        rdf::Term::NumericLiteral(n) => nibli_engine::EngineLogicalTerm::Number(*n),
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_import_simple_triple() {
66        let engine = NibliEngine::new();
67        let turtle =
68            r#"<http://example.org/adam> <http://example.org/likes> <http://example.org/bob> ."#;
69        let count = import_triples_raw(&engine, turtle).unwrap();
70        assert_eq!(count, 1);
71        let facts = engine.list_facts().unwrap();
72        assert!(!facts.is_empty());
73    }
74
75    #[test]
76    fn test_import_owl_class_hierarchy() {
77        let engine = NibliEngine::new();
78        let turtle = r#"
79<http://example.org/Dog> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <http://example.org/Animal> .
80<http://example.org/adam> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Dog> .
81"#;
82        let count = import_turtle(&engine, turtle).unwrap();
83        assert_eq!(count, 2); // 1 subsort + 1 entity sort
84    }
85
86    #[test]
87    fn test_import_numeric_literal() {
88        let engine = NibliEngine::new();
89        let turtle = r#"<http://example.org/adam> <http://example.org/age> "42"^^<http://www.w3.org/2001/XMLSchema#integer> ."#;
90        let count = import_triples_raw(&engine, turtle).unwrap();
91        assert_eq!(count, 1);
92    }
93
94    #[test]
95    fn test_import_with_prefix() {
96        let engine = NibliEngine::new();
97        let turtle = r#"
98@prefix ex: <http://example.org/> .
99ex:adam ex:likes ex:bob .
100"#;
101        let count = import_triples_raw(&engine, turtle).unwrap();
102        assert_eq!(count, 1);
103    }
104
105    #[test]
106    fn test_export_roundtrip() {
107        let engine = NibliEngine::new();
108        engine
109            .assert_fact_direct(
110                "likes".to_string(),
111                vec![
112                    nibli_engine::EngineLogicalTerm::Constant("adam".to_string()),
113                    nibli_engine::EngineLogicalTerm::Constant("bob".to_string()),
114                ],
115            )
116            .unwrap();
117        let exported = export_facts(&engine).unwrap();
118        assert!(exported.contains("likes"));
119    }
120}