use crate::error::{WasmError, WasmResult};
use crate::Triple;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstructOutputFormat {
NTriples,
Turtle,
JsonLd,
}
pub fn serialize_construct(
triples: &[Triple],
format: ConstructOutputFormat,
prefixes: &HashMap<String, String>,
) -> WasmResult<String> {
match format {
ConstructOutputFormat::NTriples => serialize_ntriples(triples),
ConstructOutputFormat::Turtle => serialize_turtle(triples, prefixes),
ConstructOutputFormat::JsonLd => serialize_construct_jsonld(triples),
}
}
pub(crate) fn serialize_ntriples(triples: &[Triple]) -> WasmResult<String> {
let mut output = String::new();
for triple in triples {
let s = format_nt_term(&triple.subject());
let p = format_nt_term(&triple.predicate());
let o = format_nt_object(&triple.object());
output.push_str(&format!("{} {} {} .\n", s, p, o));
}
Ok(output)
}
fn format_nt_term(term: &str) -> String {
if term.starts_with("_:")
|| (term.starts_with('<') && term.ends_with('>'))
|| term.starts_with('"')
{
term.to_string()
} else {
format!("<{}>", term)
}
}
fn format_nt_object(term: &str) -> String {
if term.starts_with('"')
|| term.starts_with("_:")
|| (term.starts_with('<') && term.ends_with('>'))
{
term.to_string()
} else {
format!("<{}>", term)
}
}
pub(crate) fn serialize_turtle(
triples: &[Triple],
prefixes: &HashMap<String, String>,
) -> WasmResult<String> {
let mut output = String::new();
for (prefix, iri) in prefixes {
output.push_str(&format!("@prefix {}: <{}> .\n", prefix, iri));
}
if !prefixes.is_empty() {
output.push('\n');
}
let mut by_subject: Vec<(String, Vec<(String, String)>)> = Vec::new();
for triple in triples {
let s = triple.subject();
if let Some(entry) = by_subject.iter_mut().find(|(subj, _)| *subj == s) {
entry.1.push((triple.predicate(), triple.object()));
} else {
by_subject.push((s, vec![(triple.predicate(), triple.object())]));
}
}
for (subject, po_pairs) in &by_subject {
let s = abbreviate_term(subject, prefixes);
let mut first = true;
for (predicate, object) in po_pairs {
let p = if predicate == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
"a".to_string()
} else {
abbreviate_term(predicate, prefixes)
};
let o = abbreviate_object(object, prefixes);
if first {
output.push_str(&format!("{} {} {}", s, p, o));
first = false;
} else {
output.push_str(&format!(" ;\n {} {}", p, o));
}
}
output.push_str(" .\n");
}
Ok(output)
}
pub(crate) fn abbreviate_term(term: &str, prefixes: &HashMap<String, String>) -> String {
if term.starts_with("_:") {
return term.to_string();
}
for (prefix, iri) in prefixes {
if let Some(local) = term.strip_prefix(iri.as_str()) {
return format!("{}:{}", prefix, local);
}
}
format!("<{}>", term)
}
fn abbreviate_object(term: &str, prefixes: &HashMap<String, String>) -> String {
if term.starts_with('"') || term.starts_with("_:") {
term.to_string()
} else {
abbreviate_term(term, prefixes)
}
}
pub(crate) fn serialize_construct_jsonld(triples: &[Triple]) -> WasmResult<String> {
let mut nodes: Vec<serde_json::Value> = Vec::new();
let mut by_subject: HashMap<String, Vec<(String, String)>> = HashMap::new();
for triple in triples {
by_subject
.entry(triple.subject())
.or_default()
.push((triple.predicate(), triple.object()));
}
for (subject, po_pairs) in &by_subject {
let mut node = serde_json::Map::new();
node.insert(
"@id".to_string(),
serde_json::Value::String(subject.clone()),
);
for (predicate, object) in po_pairs {
let key = if predicate == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
"@type".to_string()
} else {
predicate.clone()
};
let value = if object.starts_with('"') {
let val = extract_literal_content(object);
serde_json::json!([{"@value": val}])
} else {
serde_json::json!([{"@id": object}])
};
node.insert(key, value);
}
nodes.push(serde_json::Value::Object(node));
}
serde_json::to_string_pretty(&nodes).map_err(|e| WasmError::SerializationError(e.to_string()))
}
pub(crate) fn extract_literal_content(literal: &str) -> String {
let s = literal.trim();
if let Some(stripped) = s.strip_prefix('"') {
if let Some(end_quote) = stripped.find('"') {
return stripped[..end_quote].to_string();
}
}
s.to_string()
}