use crate::model::{is_literal_direction, Graph, TermKind};
const RDF_REIFIES: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies";
pub(crate) fn escape_literal(lex: &str) -> String {
let mut out = String::with_capacity(lex.len());
for ch in lex.chars() {
match ch {
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04X}", c as u32)),
c => out.push(c),
}
}
out
}
pub(crate) fn render_term(g: &Graph, tid: usize) -> String {
let t = &g.terms[tid];
match t.kind {
TermKind::Iri => format!("<{}>", t.value.as_deref().unwrap_or("")),
TermKind::Bnode => match &t.value {
Some(v) => format!("_:{v}"),
None => format!("_:b{tid}"),
},
TermKind::Literal => {
let lit = format!("\"{}\"", escape_literal(t.value.as_deref().unwrap_or("")));
if let Some(lang) = &t.lang {
match t.direction.as_deref().filter(|d| is_literal_direction(d)) {
Some(direction) => format!("{lit}@{lang}--{direction}"),
None => format!("{lit}@{lang}"),
}
} else if let Some(dt) = t.datatype {
format!("{lit}^^{}", render_term(g, dt))
} else {
lit }
}
TermKind::Triple => match t.reifier.and_then(|rf| g.reifier(rf)) {
Some((s, p, o)) => {
format!(
"<<( {} {} {} )>>",
render_term(g, s),
render_term(g, p),
render_term(g, o)
)
}
None => format!("_:unbound_triple_{tid}"),
},
}
}
pub fn to_nquads(g: &Graph) -> String {
let mut lines: Vec<String> = Vec::new();
for &(s, p, o, gname) in &g.quads {
let triple = format!(
"{} {} {}",
render_term(g, s),
render_term(g, p),
render_term(g, o)
);
match gname {
Some(gv) => lines.push(format!("{triple} {} .", render_term(g, gv))),
None => lines.push(format!("{triple} .")),
}
}
for &(rid, (s, p, o)) in &g.reifiers {
if g.terms
.get(rid)
.is_some_and(|term| term.kind == TermKind::Triple && term.reifier == Some(rid))
{
continue;
}
let quoted = format!(
"<<( {} {} {} )>>",
render_term(g, s),
render_term(g, p),
render_term(g, o)
);
lines.push(format!(
"{} <{RDF_REIFIES}> {quoted} .",
render_term(g, rid)
));
}
for &(r, p, v) in &g.annotations {
lines.push(format!(
"{} {} {} .",
render_term(g, r),
render_term(g, p),
render_term(g, v)
));
}
if lines.is_empty() {
String::new()
} else {
format!("{}\n", lines.join("\n"))
}
}