1use crate::render::unescape_manchester_rendering;
11use regex::Regex;
12use std::cmp::Ordering;
13
14pub const ELLIPSIS: &str = "\u{2026}";
16
17pub fn abbreviate_string(s: &str, max_len: usize) -> String {
23 if s.is_empty() {
24 return String::new();
25 }
26 if max_len == 0 {
27 return ELLIPSIS.to_string();
28 }
29 let chars: Vec<char> = s.chars().collect();
32 if chars.len() <= max_len {
33 return s.to_string();
34 }
35 chars.into_iter().take(max_len).collect::<String>() + ELLIPSIS
36}
37
38pub fn format_iso8601_utc(
40 year: u32,
41 month: u32,
42 day: u32,
43 hour: u32,
44 minute: u32,
45 second: u32,
46) -> String {
47 format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z")
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct LexicalLiteral {
53 pub lexical: String,
54 pub lang: Option<String>,
55 pub datatype: Option<String>,
56}
57
58pub fn replace_lexical_value(
60 lexical: &str,
61 lang: Option<&str>,
62 datatype: Option<&str>,
63 pattern: &Regex,
64 replacement: &str,
65) -> LexicalLiteral {
66 let new_lexical = pattern.replace_all(lexical, replacement).into_owned();
67 LexicalLiteral {
68 lexical: new_lexical,
69 lang: lang.map(str::to_string),
70 datatype: if lang.is_some() { None } else { datatype.map(str::to_string) },
71 }
72}
73
74pub fn replace_lexical_value_whole(
76 lexical: &str,
77 lang: Option<&str>,
78 datatype: Option<&str>,
79 replacement: &str,
80) -> LexicalLiteral {
81 let re = Regex::new(r"(?s)^.*$").expect("whole-string pattern");
82 replace_lexical_value(lexical, lang, datatype, &re, replacement)
83}
84
85pub fn render_entity_markdown(display_name: &str, iri: &str) -> String {
87 let name = unescape_manchester_rendering(display_name);
88 format!("[{name}]({iri})")
89}
90
91pub const DEFAULT_ANNOTATION_PROPERTY_ORDER: &[&str] = &[
93 "http://www.w3.org/2000/01/rdf-schema#label",
95 "http://www.w3.org/2004/02/skos/core#prefLabel",
96 "http://purl.org/dc/elements/1.1/title",
97 "http://www.geneontology.org/formats/oboInOwl#id",
99 "http://www.geneontology.org/formats/oboInOwl#hasAlternativeId",
100 "http://www.geneontology.org/formats/oboInOwl#hasOBONamespace",
101 "http://purl.obolibrary.org/obo/IAO_0000115",
103 "http://www.w3.org/2004/02/skos/core#definition",
104 "http://www.w3.org/2004/02/skos/core#note",
105 "http://purl.org/dc/elements/1.1/description",
106 "http://purl.org/dc/elements/1.1/rights",
108 "http://purl.org/dc/terms/license",
109 "http://purl.org/dc/elements/1.1/publisher",
110 "http://purl.org/dc/elements/1.1/creator",
111 "http://purl.org/dc/elements/1.1/contributor",
112 "http://www.w3.org/2000/01/rdf-schema#comment",
113 "http://www.w3.org/2004/02/skos/core#altLabel",
115 "http://www.w3.org/2000/01/rdf-schema#seeAlso",
116 "http://www.w3.org/2000/01/rdf-schema#isDefinedBy",
117 "http://www.geneontology.org/formats/oboInOwl#hasExactSynonym",
119 "http://www.geneontology.org/formats/oboInOwl#hasRelatedSynonym",
120 "http://www.geneontology.org/formats/oboInOwl#hasBroadSynonym",
121 "http://www.geneontology.org/formats/oboInOwl#hasNarrowSynonym",
122];
123
124fn annotation_order_index(iri: &str) -> Option<usize> {
125 DEFAULT_ANNOTATION_PROPERTY_ORDER.iter().position(|&known| known == iri)
126}
127
128pub fn cmp_annotation_property_iri(a: &str, b: &str) -> Ordering {
131 match (annotation_order_index(a), annotation_order_index(b)) {
132 (Some(i), Some(j)) => i.cmp(&j).then_with(|| a.cmp(b)),
133 (Some(_), None) => Ordering::Less,
134 (None, Some(_)) => Ordering::Greater,
135 (None, None) => a.cmp(b),
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn abbreviate_edges() {
145 assert_eq!(abbreviate_string("", 5), "");
146 assert_eq!(abbreviate_string("abc", 0), ELLIPSIS);
147 assert_eq!(abbreviate_string("hello", 5), "hello");
148 assert_eq!(abbreviate_string("hello", 3), format!("hel{ELLIPSIS}"));
149 }
150
151 #[test]
152 fn iso8601_format() {
153 assert_eq!(format_iso8601_utc(2015, 11, 20, 12, 0, 0), "2015-11-20T12:00:00Z");
154 }
155
156 #[test]
157 fn lexical_replace_preserves_lang() {
158 let re = Regex::new("world").unwrap();
159 let lit = replace_lexical_value("hello world", Some("en"), None, &re, "there");
160 assert_eq!(lit.lexical, "hello there");
161 assert_eq!(lit.lang.as_deref(), Some("en"));
162 assert!(lit.datatype.is_none());
163 }
164
165 #[test]
166 fn markdown_unescapes_manchester() {
167 assert_eq!(
168 render_entity_markdown("'has part'", "http://ex.org#hp"),
169 "[has part](http://ex.org#hp)"
170 );
171 }
172
173 #[test]
174 fn annotation_order_labels_before_comments() {
175 assert_eq!(
176 cmp_annotation_property_iri(
177 "http://www.w3.org/2000/01/rdf-schema#label",
178 "http://www.w3.org/2000/01/rdf-schema#comment"
179 ),
180 Ordering::Less
181 );
182 }
183}