use anyhow::Result;
use tensorlogic_adapters::{DomainInfo, PredicateInfo, SymbolTable};
use super::SchemaAnalyzer;
impl SchemaAnalyzer {
pub fn to_symbol_table(&self) -> Result<SymbolTable> {
let mut table = SymbolTable::new();
table.add_domain(DomainInfo::new("Literal", 10000))?;
table.add_domain(DomainInfo::new("Resource", 10000))?;
table.add_domain(DomainInfo::new("Entity", 1000))?;
for (class_iri, class_info) in &self.classes {
let domain_name = Self::iri_to_name(class_iri);
let mut domain = DomainInfo::new(&domain_name, 100);
if let Some(label) = &class_info.label {
domain = domain.with_description(label);
} else if let Some(comment) = &class_info.comment {
domain = domain.with_description(comment);
}
table.add_domain(domain)?;
}
for (prop_iri, prop_info) in &self.properties {
let pred_name = Self::iri_to_name(prop_iri);
let mut arg_domains = Vec::new();
if !prop_info.domain.is_empty() {
arg_domains.push(Self::iri_to_name(&prop_info.domain[0]));
} else {
arg_domains.push("Entity".to_string()); }
if !prop_info.range.is_empty() {
arg_domains.push(Self::iri_to_name(&prop_info.range[0]));
} else {
arg_domains.push("Entity".to_string()); }
let mut predicate = PredicateInfo::new(&pred_name, arg_domains);
if let Some(label) = &prop_info.label {
predicate = predicate.with_description(label);
} else if let Some(comment) = &prop_info.comment {
predicate = predicate.with_description(comment);
}
table.add_predicate(predicate)?;
}
Ok(table)
}
pub fn iri_to_name(iri: &str) -> String {
if let Some(hash_pos) = iri.rfind('#') {
iri[hash_pos + 1..].to_string()
} else if let Some(slash_pos) = iri.rfind('/') {
iri[slash_pos + 1..].to_string()
} else {
iri.to_string()
}
}
}
pub fn symbol_table_to_turtle(table: &SymbolTable, base_iri: &str) -> String {
let mut output = String::new();
output.push_str("@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n");
output.push_str("@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n");
output.push_str("@prefix owl: <http://www.w3.org/2002/07/owl#> .\n");
output.push_str("@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n");
output.push_str(&format!("@prefix ex: <{}> .\n\n", base_iri));
let standard_types = ["Literal", "Resource", "Entity", "Value"];
for (name, domain) in &table.domains {
if standard_types.contains(&name.as_str()) {
continue;
}
output.push_str(&format!("ex:{} a rdfs:Class", name));
if let Some(desc) = &domain.description {
output.push_str(&format!(
" ;\n rdfs:label \"{}\"",
escape_turtle_string(desc)
));
}
output.push_str(&format!(
" ;\n rdfs:comment \"Cardinality: {}\"",
domain.cardinality
));
output.push_str(" .\n\n");
}
for (name, predicate) in &table.predicates {
output.push_str(&format!("ex:{} a rdf:Property", name));
if !predicate.arg_domains.is_empty() {
let domain_name = &predicate.arg_domains[0];
if !standard_types.contains(&domain_name.as_str()) {
output.push_str(&format!(" ;\n rdfs:domain ex:{}", domain_name));
}
}
if predicate.arg_domains.len() > 1 {
let range_name = &predicate.arg_domains[1];
if !standard_types.contains(&range_name.as_str()) {
output.push_str(&format!(" ;\n rdfs:range ex:{}", range_name));
}
}
if let Some(desc) = &predicate.description {
output.push_str(&format!(
" ;\n rdfs:label \"{}\"",
escape_turtle_string(desc)
));
}
output.push_str(" .\n\n");
}
output
}
pub fn symbol_table_to_json(table: &SymbolTable) -> Result<String> {
serde_json::to_string_pretty(table)
.map_err(|e| anyhow::anyhow!("JSON serialization error: {}", e))
}
pub fn symbol_table_from_json(json: &str) -> Result<SymbolTable> {
serde_json::from_str(json).map_err(|e| anyhow::anyhow!("JSON deserialization error: {}", e))
}
fn escape_turtle_string(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
}
#[cfg(test)]
mod tests {
use super::*;
use tensorlogic_adapters::{DomainInfo, PredicateInfo};
#[test]
fn test_symbol_table_to_turtle_basic() {
let mut table = SymbolTable::new();
table
.add_domain(DomainInfo::new("Person", 100).with_description("A human being"))
.expect("unwrap");
table
.add_domain(DomainInfo::new("Organization", 50))
.expect("unwrap");
table
.add_predicate(PredicateInfo::new(
"worksFor",
vec!["Person".to_string(), "Organization".to_string()],
))
.expect("unwrap");
let turtle = symbol_table_to_turtle(&table, "http://example.org/");
assert!(turtle.contains("@prefix rdf:"));
assert!(turtle.contains("@prefix rdfs:"));
assert!(turtle.contains("ex:Person a rdfs:Class"));
assert!(turtle.contains("ex:Organization a rdfs:Class"));
assert!(turtle.contains("ex:worksFor a rdf:Property"));
assert!(turtle.contains("rdfs:domain ex:Person"));
assert!(turtle.contains("rdfs:range ex:Organization"));
assert!(turtle.contains("A human being"));
}
#[test]
fn test_symbol_table_to_turtle_skips_standard_types() {
let mut table = SymbolTable::new();
table
.add_domain(DomainInfo::new("Literal", 10000))
.expect("unwrap");
table
.add_domain(DomainInfo::new("Entity", 1000))
.expect("unwrap");
table
.add_domain(DomainInfo::new("Person", 100))
.expect("unwrap");
let turtle = symbol_table_to_turtle(&table, "http://example.org/");
assert!(!turtle.contains("ex:Literal"));
assert!(!turtle.contains("ex:Entity"));
assert!(turtle.contains("ex:Person"));
}
#[test]
fn test_symbol_table_json_roundtrip() {
let mut table = SymbolTable::new();
table
.add_domain(DomainInfo::new("Person", 100).with_description("A person"))
.expect("unwrap");
table
.add_predicate(
PredicateInfo::new("knows", vec!["Person".to_string(), "Person".to_string()])
.with_description("Knows relationship"),
)
.expect("unwrap");
let json = symbol_table_to_json(&table).expect("unwrap");
let imported = symbol_table_from_json(&json).expect("unwrap");
assert_eq!(table.domains.len(), imported.domains.len());
assert_eq!(table.predicates.len(), imported.predicates.len());
assert!(imported.domains.contains_key("Person"));
assert!(imported.predicates.contains_key("knows"));
}
#[test]
fn test_escape_turtle_string() {
assert_eq!(escape_turtle_string("simple"), "simple");
assert_eq!(
escape_turtle_string("with \"quotes\""),
"with \\\"quotes\\\""
);
assert_eq!(escape_turtle_string("line1\nline2"), "line1\\nline2");
assert_eq!(escape_turtle_string("with\\backslash"), "with\\\\backslash");
}
}