1use crate::patch::best_namespace_match;
7use crate::span::short_name_from_iri;
8use std::collections::BTreeMap;
9
10const OWL_NS: &str = "http://www.w3.org/2002/07/owl#";
11const RDFS_NS: &str = "http://www.w3.org/2000/01/rdf-schema#";
12const RDF_NS: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
13const XSD_NS: &str = "http://www.w3.org/2001/XMLSchema#";
14
15pub fn split_iri(iri: &str) -> (String, String) {
17 if let Some((ns, local)) = iri.rsplit_once('#') {
18 return (format!("{ns}#"), local.to_string());
19 }
20 if let Some((ns, local)) = iri.rsplit_once('/') {
21 return (format!("{ns}/"), local.to_string());
22 }
23 (String::new(), iri.to_string())
24}
25
26pub fn render_entity_iri(iri: &str, namespaces: &BTreeMap<String, String>) -> String {
28 if let Some(curie) = render_as_curie(iri, namespaces) {
29 return curie;
30 }
31 let short = short_name_from_iri(iri);
32 if !short.is_empty() && short != iri {
33 return short;
34 }
35 format!("<{iri}>")
36}
37
38pub fn render_as_curie(iri: &str, namespaces: &BTreeMap<String, String>) -> Option<String> {
40 let mut ns = namespaces.clone();
41 ns.entry("owl".into()).or_insert_with(|| OWL_NS.to_string());
42 ns.entry("rdfs".into()).or_insert_with(|| RDFS_NS.to_string());
43 ns.entry("rdf".into()).or_insert_with(|| RDF_NS.to_string());
44 ns.entry("xsd".into()).or_insert_with(|| XSD_NS.to_string());
45 best_namespace_match(iri, &ns).map(|(prefix, base)| {
46 let local = &iri[base.len()..];
47 format!("{prefix}:{local}")
48 })
49}
50
51pub fn expand_prefixed_iri(token: &str, namespaces: &BTreeMap<String, String>) -> Option<String> {
53 let token = token.trim();
54 if token.is_empty() {
55 return None;
56 }
57 if token.starts_with('<') && token.ends_with('>') && token.len() >= 2 {
58 return Some(token[1..token.len() - 1].to_string());
59 }
60 if token.contains("://") {
61 return Some(token.to_string());
62 }
63 let (prefix, local) = token.split_once(':')?;
64 let base = namespaces.get(prefix)?;
65 Some(format!("{base}{local}"))
66}
67
68pub fn match_prefix<'a>(
70 iri: &str,
71 namespaces: &'a BTreeMap<String, String>,
72) -> Option<(&'a str, &'a str)> {
73 best_namespace_match(iri, namespaces)
74}
75
76pub fn escape_manchester_rendering(original: &str) -> String {
78 let mut rendering = original.replace('\\', "\\\\");
79 rendering = rendering.replace('\'', "\\'");
80 rendering = rendering.replace('"', "\\\"");
81 let needs_quotes = original.chars().any(|c| {
82 matches!(
83 c,
84 ' ' | '\\' | ',' | '<' | '>' | '=' | '^' | '@' | '{' | '}' | '[' | ']' | '(' | ')'
85 )
86 });
87 if needs_quotes {
88 format!("'{rendering}'")
89 } else {
90 rendering
91 }
92}
93
94pub fn unescape_manchester_rendering(rendering: &str) -> String {
96 let mut s = rendering.to_string();
97 if s.starts_with('\'') && s.ends_with('\'') && s.len() >= 2 {
98 s = s[1..s.len() - 1].to_string();
99 }
100 s = s.replace("\\\"", "\"");
101 s = s.replace("\\'", "'");
102 s = s.replace("\\\\", "\\");
103 s
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 fn ns() -> BTreeMap<String, String> {
111 BTreeMap::from([
112 ("ex".to_string(), "http://example.org/#".to_string()),
113 ("owl".to_string(), OWL_NS.to_string()),
114 ])
115 }
116
117 #[test]
118 fn split_hash_and_slash() {
119 assert_eq!(
120 split_iri("http://example.org#Person"),
121 ("http://example.org#".into(), "Person".into())
122 );
123 assert_eq!(
124 split_iri("http://example.org/obo/GO_1"),
125 ("http://example.org/obo/".into(), "GO_1".into())
126 );
127 }
128
129 #[test]
130 fn render_curie_and_builtin() {
131 assert_eq!(render_entity_iri("http://example.org/#Person", &ns()), "ex:Person");
132 assert_eq!(
133 render_entity_iri("http://www.w3.org/2002/07/owl#Thing", &BTreeMap::new()),
134 "owl:Thing"
135 );
136 }
137
138 #[test]
139 fn expand_and_match_prefix() {
140 let namespaces = ns();
141 assert_eq!(
142 expand_prefixed_iri("ex:Person", &namespaces).as_deref(),
143 Some("http://example.org/#Person")
144 );
145 let (p, base) = match_prefix("http://example.org/#Person", &namespaces).unwrap();
146 assert_eq!(p, "ex");
147 assert_eq!(base, "http://example.org/#");
148 }
149
150 #[test]
151 fn manchester_escape_roundtrip() {
152 assert_eq!(escape_manchester_rendering("Person"), "Person");
153 assert_eq!(escape_manchester_rendering("has part"), "'has part'");
154 assert_eq!(unescape_manchester_rendering(&escape_manchester_rendering("a'b")), "a'b");
155 }
156}